#include "MapCallback.h"

#include <osg/LineWidth>
#include <osg/Vec3d>
#include <osg/Camera>
#include <osg/Matrix>
#include <osg/Shape>
#include <osg/ShapeDrawable>
#include <osgText/Text>
#include <osgUtil/LineSegmentIntersector>
#include <osgUtil/IntersectionVisitor>
#include <osgViewer/Viewer>
#include <osgEarth/Terrain>
#include <osgEarth/MapNode>
#include <osgEarthSymbology/Geometry>
#include <osgEarth/GLUtils>

#include <odv/odvLog/LogStream.h>

using namespace appmodule;

////////////////////////////  MapMouseMoveCallback
void MapMouseMoveCallback::convertToDMS(double decimalDegree, int& degrees, int& minutes, double& seconds) {
	degrees = static_cast<int>(decimalDegree); // 度部分
	double temp = std::abs((decimalDegree - degrees)) * 60; // 计算分部分
	minutes = static_cast<int>(temp); // 分部分
	seconds = (temp - minutes) * 60; // 秒部分
}

MapMouseMoveCallback::MapMouseMoveCallback(int viewIndex)
	: _viewIndex(viewIndex)
{
}

void MapMouseMoveCallback::operator()(odv::Viewer* viewer, odv::EventType eventType, odv::MouseButton button, int x, int y)
{
	if (!_callback) return;

	if (eventType == odv::EventType::MOVE) {
		osg::Vec3d world;
		if (viewer->getTerrain()->getWorldCoordsUnderMouse(viewer->getOsgCamera()->getView(), x * 1.0, y * 1.0, world)) {
			osgEarth::GeoPoint mapPoint;
			mapPoint.fromWorld(viewer->getMapSRS(), world);
			mapPoint.makeGeographic();

			_callback(mapPoint.x(), mapPoint.y(), mapPoint.z(), _viewIndex);
		}
	}
}

////////////////////////////MapMouseClickedCallback
MapMouseClickedCallback::MapMouseClickedCallback(int viewIndex)
	: _viewIndex(viewIndex)
{
}

void MapMouseClickedCallback::operator()
(odv::Viewer* viewer, odv::EventType eventType, odv::MouseButton button, int x, int y) const
{
	if (eventType == odv::EventType::PUSH) {
		_mapClickedCallback(_viewIndex);
	}
}

//////////////////////////MapMouseWheelClickedCallback
MapMouseWheelClickedCallback::MapMouseWheelClickedCallback(int viewIndex)
	: _viewIndex(viewIndex)
{
}

void MapMouseWheelClickedCallback::operator()
(odv::Viewer* viewer, odv::EventType eventType, odv::MouseButton button, int x, int y) const
{
	if (!_mapWheelCallback) return;

	if (eventType == odv::EventType::SCROLL) {
		auto extent = viewer->getViewLatLonExtent();
		_mapWheelCallback(_viewIndex, extent.getWidth(), extent.getHeight());
	}
}

////////////////////////////  MapPointCallback
MapPointCallback::MapPointCallback(std::shared_ptr<odv::Viewer> viewer, int viewIndex)
	: _viewer(viewer)
	, _viewIndex(viewIndex)
	, _wgs84(osgEarth::SpatialReference::create("wgs84"))
{
}

MapPointCallback::~MapPointCallback()
{
	removePoint();
}

void MapPointCallback::operator()(odv::Viewer* viewer, odv::EventType eventType, odv::MouseButton button, int x, int y)
{
	if (!_callback) return;

	if (eventType == odv::EventType::PUSH)
	{
		_pushedCoords = { x, y };
	} else if (eventType == odv::EventType::RELEASE)
	{
		if (_pushedCoords && _pushedCoords == osg::Vec2i{ x, y })
		{
			osg::Vec3d world;
			if (viewer->getTerrain()->getWorldCoordsUnderMouse(viewer->getOsgCamera()->getView(), x * 1.0, y * 1.0, world))
			{
				osgEarth::GeoPoint mapPoint;
				mapPoint.fromWorld(viewer->getMapSRS(), world);
				mapPoint.makeGeographic();
				_callback(mapPoint.x(), mapPoint.y(), _viewIndex);// lon lat

				addPoint({ mapPoint.x(), mapPoint.y(), 0 });
			}
		}

		_pushedCoords.reset();
	} 
	//else if (eventType == odv::EventType::MOVE)
	//{
	//	osg::Vec3d world;
	//	if (viewer->getTerrain()->getWorldCoordsUnderMouse(viewer->getOsgCamera()->getView(), x * 1.0, y * 1.0, world))
	//	{
	//		osgEarth::GeoPoint mapPoint;
	//		mapPoint.fromWorld(viewer->getMapSRS(), world);
	//		mapPoint.makeGeographic();

	//		updatePoint({ mapPoint.x(), mapPoint.y(), 0 });
	//	}
	//}
}

void MapPointCallback::startCallback()
{
}

void MapPointCallback::stopCallback()
{
	removePoint();
}

void MapPointCallback::addPoint(const osg::Vec3d& p)
{
	auto viewer = _viewer.lock();
	if (!viewer) return;
	auto mapNode = viewer->getMapNode();
	if (!mapNode) return;

	removePoint();

	_pointNode = new osg::MatrixTransform;
	mapNode->addChild(_pointNode);
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	_pointNode->addChild(geode);

	auto mapSRS = mapNode->getMapSRS();
	osg::Vec3d point;
	if (mapSRS->isProjected())
	{
		_wgs84->transform(p, mapSRS, point);
	} else
	{
		_wgs84->transformToWorld(p, point);
	}
	_pointNode->setMatrix(osg::Matrixd::translate(point));

	if (false)
	{
		const static float radius = 3000;
		osg::ref_ptr<osg::Shape> shape = new osg::Sphere({ 0, 0, 0 }, radius);
		osg::ref_ptr<osg::ShapeDrawable> sd = new osg::ShapeDrawable(shape);
		sd->setColor({ 1, 0, 0,1 });
		geode->addDrawable(sd);
	} else
	{
		osg::ref_ptr<osgText::Text> text = new osgText::Text;
		geode->addDrawable(text);

		text->setCharacterSizeMode(osgText::TextBase::CharacterSizeMode::SCREEN_COORDS);
		text->setCharacterSize(25);
		text->setText("+", osgText::String::ENCODING_UTF8);
		text->setColor({ 1, 0, 0, 1 });
		text->setPosition({ 0, 0, 0 });
		text->setBackdropType(osgText::Text::NONE);
		text->setAlignment(osgText::TextBase::AlignmentType::CENTER_CENTER);
		text->setAutoRotateToScreen(true);
	}
	auto stateSet = geode->getOrCreateStateSet();
	stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
	stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
	stateSet->setRenderBinDetails(99999 + 1, "DepthSortedBin");
}

void MapPointCallback::updatePoint(const osg::Vec3d& p)
{
	if (!_pointNode) return;

	auto viewer = _viewer.lock();
	if (!viewer) return;
	auto mapNode = viewer->getMapNode();
	if (!mapNode) return;

	auto mapSRS = mapNode->getMapSRS();
	osg::Vec3d point;
	if (mapSRS->isProjected())
	{
		_wgs84->transform(p, mapSRS, point);
	} else
	{
		_wgs84->transformToWorld(p, point);
	}
	_pointNode->setMatrix(osg::Matrixd::translate(point));
}

void MapPointCallback::removePoint()
{
	auto viewer = _viewer.lock();
	if (!viewer) return;
	auto mapNode = viewer->getMapNode();
	if (!mapNode) return;

	if (_pointNode)
	{
		mapNode->removeChild(_pointNode);
		_pointNode.release();
	}
}

////////////////////////////  MapSectionCallback
MapSectionCallback::MapSectionCallback(std::shared_ptr<odv::Viewer> viewer, int viewIndex)
	: _viewIndex(viewIndex)
	, _viewer(viewer)
{
}

MapSectionCallback::~MapSectionCallback()
{
	removeLine();
}

void MapSectionCallback::operator()(odv::Viewer* viewer, odv::EventType eventType, odv::MouseButton button, int x, int y)
{
	osg::Vec3d world;
	if (!viewer->getTerrain()->getWorldCoordsUnderMouse(viewer->getOsgCamera()->getView(), x * 1.0, y * 1.0, world)) {
		return;
	}

	if (eventType == odv::EventType::PUSH)
	{
		_pushedCoords = { x, y };
	}
	if (eventType == odv::EventType::RELEASE)
	{
		if (_pushedCoords && _pushedCoords == osg::Vec2i(x, y))
		{
			auto mapSRS = viewer->getMapNode()->getMapSRS();
			auto geoSRS = mapSRS->getGeographicSRS();

			if (_points.empty())
			{
				osgEarth::GeoPoint mapPoint;
				mapPoint.fromWorld(mapSRS, world);
				mapPoint.makeGeographic();

				osg::Vec3d p{ mapPoint.x(), mapPoint.y(), mapPoint.z() };
				_points.push(p);
				if (!_lineNode)
				{
					addLine(p, p);
				}
			} else
			{
				auto p1 = _points.top();
				osgEarth::GeoPoint mapPoint;
				mapPoint.fromWorld(mapSRS, world);
				mapPoint.makeGeographic();
				osg::Vec3d p2{ mapPoint.x(), mapPoint.y(), mapPoint.z() };
				while (!_points.empty())
				{
					_points.pop();
				}

				updateLine(p1, p2);

				if (_callback)
				{
					_callback({ p1.x(), p1.y() }, { p2.x(), p2.y() }, _viewIndex);
				}
			}
		}
		_pushedCoords.reset();
	} else if (eventType == odv::EventType::MOVE)
	{
		if (!_points.empty())
		{
			auto mapSRS = viewer->getMapNode()->getMapSRS();
			auto geoSRS = mapSRS->getGeographicSRS();

			auto& p1 = _points.top();
			osgEarth::GeoPoint mapPoint(mapSRS, world, osgEarth::ALTMODE_ABSOLUTE);
			osgEarth::GeoPoint geoPoint;
			mapPoint.transform(geoSRS, geoPoint);
			osg::Vec3d p2{ geoPoint.x(), geoPoint.y(), geoPoint.z() };
			updateLine(p1, p2);
		}
	}
}

void MapSectionCallback::startCallback()
{
}

void MapSectionCallback::stopCallback()
{
	removeLine();
}

void MapSectionCallback::addLine(const osg::Vec3& p1, const osg::Vec3& p2)
{
	auto viewer = _viewer.lock();
	if (!viewer) return;
	auto mapNode = viewer->getMapNode();
	if (!mapNode) return;

	if (_lineNode)
	{
		mapNode->removeChild(_lineNode);
		_lineNode.release();
	}

	_lineNode = new osg::Geode;
	mapNode->addChild(_lineNode);

	osg::ref_ptr<osgEarth::SpatialReference> wgs84 = osgEarth::SpatialReference::create("wgs84");
	auto mapSRS = mapNode->getMapSRS();
	std::vector<osg::Vec3d> points{ p1, p2 };
	if (mapSRS->isProjected())
	{
		wgs84->transform(points, mapSRS);
	} else
	{
		for (auto& p : points)
		{
			wgs84->transformToWorld(p, p);
		}
	}

	osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
	_lineNode->addDrawable(geom);
	geom->setDataVariance(osg::Object::DYNAMIC);
	geom->setUseVertexBufferObjects(true);
	osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(points.size());
	vertices->assign(points.begin(), points.end());
	osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
	colors->push_back({ 1, 0, 0, 1 });

	geom->setVertexArray(vertices);
	geom->setColorArray(colors, osg::Array::BIND_OVERALL);
	geom->addPrimitiveSet(new osg::DrawArrays(GL_LINES, 0, vertices->size()));

	auto stateSet = _lineNode->getOrCreateStateSet();
	stateSet->setRenderBinDetails(99999 + 1, "DepthSortedBin");
	stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
	stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
	stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
	stateSet->setAttributeAndModes(new osg::LineWidth(3));
}

void MapSectionCallback::updateLine(const osg::Vec3& p1, const osg::Vec3& p2)
{
	if (!_lineNode) return;
	auto geom = dynamic_cast<osg::Geometry*>(_lineNode->getDrawable(0));
	if (!geom) return;

	auto viewer = _viewer.lock();
	if (!viewer) return;
	auto mapNode = viewer->getMapNode();
	if (!mapNode) return;

	osg::ref_ptr<osgEarth::SpatialReference> wgs84 = osgEarth::SpatialReference::create("wgs84");
	auto mapSRS = mapNode->getMapSRS();
	std::vector<osg::Vec3d> points{ p1, p2 };
	if (mapSRS->isProjected())
	{
		wgs84->transform(points, mapSRS);
	} else
	{
		for (auto& p : points)
		{
			wgs84->transformToWorld(p, p);
		}
	}

	auto vertices = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());
	if (!vertices) return;
	vertices->at(0) = points[0];
	vertices->at(1) = points[1];
	vertices->dirty();
}

void MapSectionCallback::removeLine()
{
	auto viewer = _viewer.lock();
	if (!viewer) return;
	auto mapNode = viewer->getMapNode();
	if (!mapNode) return;
	if (_lineNode)
	{
		mapNode->removeChild(_lineNode);
		_lineNode.release();
	}
}

MapTimeCallback::MapTimeCallback(std::shared_ptr<odv::Viewer> viewer, int viewIndex)
	: MapPointCallback(viewer, viewIndex)
{
}

MapTimeCallback::~MapTimeCallback()
{
}